﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.Script.Serialization;

namespace CSharp.Infrastructure
{
    /// <summary>
    /// 微信模板消息通知
    /// </summary>
    public class WeChatHelper
    {
        private static object lockObj = new object();
        private static JavaScriptSerializer js = new JavaScriptSerializer() { MaxJsonLength = int.MaxValue };

        private string access_token_grant_type_client_credential = string.Empty;

        // 有效时间2小时，90分钟以上就重新读取了
        private DateTime update_time = DateTime.MinValue;

        // 微信配置相关
        private string appid = string.Empty;
        private string secret = string.Empty;

        // 微信 jsticket
        private static string js_ticket = string.Empty;
        private static DateTime update_time_jsticket = DateTime.MinValue;

        /// <summary>
        /// 初始化数据
        /// </summary>
        /// <param name="configuration"></param>
        public WeChatHelper(string appid, string secret)
        {
            this.appid = appid;
            this.secret = secret;
        }

        /// <summary>
        /// 获取access_token
        /// grant_type=client_credential
        /// </summary>
        /// <returns></returns>
        public string GetAccessToken_grant_type_client_credential(bool is_new = false)
        {
            lock (lockObj)
            {
                if (is_new || update_time.AddMinutes(90) < DateTime.Now)
                {
                    Update_access_token_grant_type_client_credential();
                }

                return access_token_grant_type_client_credential;
            }
        }

        /// <summary>
        /// 更新token
        /// </summary>
        public void Update_access_token_grant_type_client_credential()
        {
            try
            {
                string url = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}";
                HttpHelper helper = new HttpHelper();
                string result = helper.GetAndGetHtml(url, null, null, true, Encoding.UTF8);

                Console.WriteLine("Update_access_token result " + result);
                var accessInfo = js.Deserialize<TokenInfo>(result);
                access_token_grant_type_client_credential = accessInfo.access_token;

                if (!string.IsNullOrEmpty(access_token_grant_type_client_credential))
                {
                    update_time = DateTime.Now;
                }
            }
            catch (Exception e)
            {
                Logs.WriteLog(LogType.Error, "Update_access_token error " + e.ToString());
            }
        }

        /// <summary>
        /// 获取 getwxacodeunlimit
        /// 返回 base64或空字符串
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="send_item"></param>
        /// <returns></returns>
        public string GetWeChatLittAppQR(string access_token, MiniProgramItem send_item)
        {
            string url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + access_token;
            string postData = js.Serialize(send_item);

            HttpHelper helper = new HttpHelper();
            var data = helper.PostAndGetByte(url, postData, "application/json", null, false);
            if (data.Length < 1000)
            {
                // 需要更新token
                return string.Empty;
            }

            return Convert.ToBase64String(data);
        }

        /// <summary>
        /// 根据Code读取 OpenId
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public wechat_auth_info GetOpenId(string code)
        {
            string url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code";
            try
            {
                var result = HttpClientHelper.GetResponse(url);

                Logs.WriteLog(LogType.Info, "result " + result);
                var authInfo = js.Deserialize<wechat_auth_info>(result);

                return authInfo;
            }
            catch (Exception ex)
            {
                Logs.WriteLog(LogType.Error, "ex " + ex.ToString());
                return null;
            }
        }

        /// <summary>
        /// 小程序 code转 openid
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public MiniProgramItem_wechat_auth_info GetOpenId_MiniProgram(string code)
        {
            string url = $"https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";
            HttpHelper helper = new HttpHelper();
            string result = helper.GetAndGetHtml(url, null, null, true, Encoding.UTF8);

            Console.WriteLine("jscode2session result " + result);
            var accessInfo = js.Deserialize<MiniProgramItem_wechat_auth_info>(result);

            return accessInfo;
        }

        /// <summary>
        /// 获取用户关注情况
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="open_id"></param>
        /// <returns></returns>
        public SubscribeResult GetUserSubscribe(string access_token, string open_id)
        {
            string url = $"https://api.weixin.qq.com/cgi-bin/user/info?access_token={access_token}&openid={open_id}&lang=zh_CN";
            HttpHelper helper = new HttpHelper();
            string result = helper.GetAndGetHtml(url, null, null, false, Encoding.UTF8);
            Console.WriteLine("CheckSubscribeItem result " + result);

            return js.Deserialize<SubscribeResult>(result);
        }

        /// <summary>
        /// 获取用户信息
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="open_id"></param>
        /// <returns></returns>
        public WxUserInfo GetUserInfo(string access_token, string open_id)
        {
            string url = $"https://api.weixin.qq.com/sns/userinfo?access_token={access_token}&openid={open_id}&lang=zh_CN";
            HttpHelper helper = new HttpHelper();
            string result = helper.GetAndGetHtml(url, null, null, false, Encoding.UTF8);
            Console.WriteLine("CheckSubscribeItem result " + result);

            return js.Deserialize<WxUserInfo>(result);
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="access_token"></param>
        /// <param name="send_item"></param>
        /// <returns>
        /// 3-未关注
        /// 1-可能过期 token
        /// </returns>
        public int SendMsg(string access_token, SendTemplate send_item)
        {
            Console.WriteLine("send SendTemplate sendItem " + js.Serialize(send_item));
            try
            {
                string url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token;
                string postData = js.Serialize(send_item);

                HttpHelper helper = new HttpHelper();
                string result = helper.PostAndGetHtml(url, postData, "application/x-www-form-urlencoded", null, false, Encoding.UTF8, Encoding.UTF8);

                // {"errcode":0,"errmsg":"ok","msgid":1695927235804758023}
                // {"errcode":43004,"errmsg":"require subscribe rid: 6002ae34-790727f5-44a0508e"}
                Console.WriteLine("send SendTemplate result " + result);
                if (result.Contains("require subscribe"))
                {
                    // 未关注
                    return 3;
                }
                else if (result.Contains("\"errcode\":0,"))
                {
                    // 失败，需要重新获取token
                    return 0;
                }
                else
                {
                    // 可能是过期的情况
                    return 1;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("send template error " + e.ToString());
                return 2;
            }
        }

        /// <summary>
        /// wx-open-launch-weapp 标签
        /// wx.config需要的数据
        /// 通过 config 接口注入权限验证配置并申请所需开放标签
        /// https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html
        /// </summary>
        /// <returns></returns>
        public JsParamater GetWxJsParamater(string url)
        {
            var param = new JsParamater
            {
                appId = appid,
                timeStamp = GenerateTimeStamp(),
                nonceStr = GenerateNonceStr(),
                signature = "",
            };

            GetJsTicket();
            string string1 = $"jsapi_ticket={js_ticket}&noncestr={param.nonceStr}&timestamp={param.timeStamp}&url={url}";
            Console.WriteLine("js_str string1 " + string1);
            string signature = GetSha1(string1).ToLower();
            param.signature = signature;

            return param;
        }

        /// <summary>
        /// 获取access_token
        /// </summary>
        /// <returns></returns>
        private string GetJsTicket()
        {
            GetAccessToken_grant_type_client_credential();
            lock (lockObj)
            {
                if (update_time_jsticket.AddMinutes(90) < DateTime.Now)
                {
                    Update_jsticket();
                }

                return js_ticket;
            }
        }

        /// <summary>
        /// 更新token
        /// </summary>
        private void Update_jsticket()
        {
            try
            {
                string url = $"https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={access_token_grant_type_client_credential}&type=jsapi";
                HttpHelper helper = new HttpHelper();
                string result = helper.GetAndGetHtml(url, null, null, true, Encoding.UTF8);

                Console.WriteLine("Update_jsticket result " + result);
                var accessInfo = js.Deserialize<TicketInfo>(result);
                if (accessInfo == null || string.IsNullOrEmpty(accessInfo.ticket))
                {
                    // 再次执行
                    result = helper.GetAndGetHtml(url, null, null, true, Encoding.UTF8);

                    Console.WriteLine("Update_jsticket 2 result " + result);
                    accessInfo = js.Deserialize<TicketInfo>(result);
                }

                if (accessInfo != null && !string.IsNullOrEmpty(access_token_grant_type_client_credential))
                {
                    js_ticket = accessInfo.ticket;
                    update_time_jsticket = DateTime.Now;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Update_jsticket error " + e.ToString());
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        private string GetSha1(string str)
        {
            var buffer = Encoding.UTF8.GetBytes(str);
            var data = SHA1.Create().ComputeHash(buffer);

            var sb = new StringBuilder();
            foreach (var t in data)
            {
                sb.Append(t.ToString("X2"));
            }

            return sb.ToString();
        }

        /// <summary>
        /// 时间搓
        /// </summary>
        /// <returns></returns>
        private string GenerateTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        /// <summary>
        /// 随机字符串
        /// </summary>
        /// <returns></returns>
        private string GenerateNonceStr()
        {
            byte[] b = new byte[4];
            new RNGCryptoServiceProvider().GetBytes(b);
            Random r = new Random(BitConverter.ToInt32(b, 0));
            string s = null, str = "";
            str += "0123456789";
            str += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            for (int i = 0; i < 16; i++)
            {
                s += str.Substring(r.Next(0, str.Length - 1), 1);
            }

            return s;
        }
    }

    /// <summary>
    /// 微信控制接口返回
    /// </summary>
    public class TicketInfo
    {
        /// <summary>
        /// ticket
        /// </summary>
        public string ticket { get; set; }

        /// <summary>
        /// 有效时间
        /// </summary>
        public int expires_in { get; set; }
    }

    public class JsParamater
    {
        public string appId { get; set; }

        public string timeStamp { get; set; }

        public string nonceStr { get; set; }

        public string signature { get; set; }

        public string package { get; set; }

        public string signType { get; set; }
    }

    public class wechat_auth_info
    {
        public string access_token { get; set; }

        public string refresh_token { get; set; }

        public string openid { get; set; }

        public string scope { get; set; }

        public int expires_in { get; set; }
    }


    public class SendTemplate
    {
        /// <summary>
        /// 收消息的用户的 open_id
        /// </summary>
        public string touser { get; set; }

        /// <summary>
        /// 平台申请的目标ID
        /// </summary>
        public string template_id { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string url { get; set; }

        public Dictionary<string, DataItem> data { get; set; }
    }

    public class DataItem
    {
        public string value { get; set; }
        public string color { get; set; }
    }

    public class WxUserInfo
    {
        public string openid { get; set; }

        public string nickname { get; set; }

        /// <summary>
        /// 用户的性别，值为1时是男性，值为2时是女性，值为0时是未知
        /// </summary>
        public int sex { get; set; }
        public string province { get; set; }
        public string city { get; set; }

        /// <summary>
        /// 国家，如中国为CN
        /// </summary>
        public string country { get; set; }

        /// <summary>
        /// 用户头像，最后一个数值代表正方形头像大小（有0、46、64、96、132数值可选，0代表640*640正方形头像），用户没有头像时该项为空。若用户更换头像，原有头像 URL 将失效。
        /// </summary>
        public string headimgurl { get; set; }

        /// <summary>
        /// 用户特权信息，json 数组，如微信沃卡用户为（chinaunicom）
        /// </summary>
        public List<string> privilege { get; set; }

        /// <summary>
        /// 只有在用户将公众号绑定到微信开放平台帐号后，才会出现该字段。
        /// </summary>
        public string unionid { get; set; }
    }

    public class SubscribeResult
    {
        public int subscribe { get; set; }

        public string openid { get; set; }

        public string unionid { get; set; }
    }

    /// <summary>
    /// 生成小程序二维码的请求对象
    /// </summary>
    public class MiniProgramItem
    {
        public string scene { get; set; }

        public string page { get; set; }

        /// <summary>
        /// 要打开的小程序版本。正式版为 release，体验版为 trial，开发版为 develop
        /// </summary>
        public string env_version { get; set; } = "develop";

        /// <summary>
        /// release 不能false
        /// </summary>
        public bool check_path { get; set; } = false;
    }

    /// <summary>
    /// openid string 用户唯一标识
    /// session_key string 会话密钥
    /// unionid string 用户在开放平台的唯一标识符，若当前小程序已绑定到微信开放平台帐号下会返回，详见 UnionID 机制说明。
    /// errcode number  错误码
    /// errmsg  string 错误信息
    /// </summary>
    public class MiniProgramItem_wechat_auth_info
    {
        public string openid { get; set; }

        public string session_key { get; set; }

        public string unionid { get; set; }

        public int errcode { get; set; }

        public string errmsg { get; set; }
    }

    /// <summary>
    /// 微信控制接口返回
    /// </summary>
    public class TokenInfo
    {
        /// <summary>
        /// access_token
        /// </summary>
        public string access_token { get; set; }

        /// <summary>
        /// 有效时间
        /// </summary>
        public int expires_in { get; set; }
    }
}